import 'package:dio/dio.dart';

/// 本文件集中封装了调用 GitCode API 所需的 HTTP 客户端、数据模型
/// 以及通用的解析逻辑，便于在 UI 层直接复用，避免重复拼装请求。

/// GitCode API 的轻量封装，负责统一请求参数、超时和错误处理。
class GitCodeApiClient {
  GitCodeApiClient({Dio? dio})
      : _dio = dio ??
            Dio(
              BaseOptions(
                // 默认基准端点指向 GitCode v5 API，所有请求在此路径下拼接。
                baseUrl: 'https://api.gitcode.com/api/v5',
                // 连接和读取阶段都限定为 5 秒，能在弱网场景下快速失败，提示用户重试。
                connectTimeout: const Duration(seconds: 5),
                receiveTimeout: const Duration(seconds: 5),
              ),
            );

  final Dio _dio;

  /// 通过登录名获取用户详情，可选地携带 token 进行授权访问。
  Future<GitCodeUser> fetchUser(
    String username, {
    String? personalToken,
    bool allowSearchFallback = true,
  }) async {
    // 统一去除空白，避免因为用户误输入空格导致请求 404。
    final trimmed = username.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('用户名不能为空');
    }

    try {
      final response = await _dio.get<Map<String, dynamic>>(
        '/users/${Uri.encodeComponent(trimmed)}',
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          // 允许 4xx 结果进入响应分支，以便输出更友好的提示。
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      switch (statusCode) {
        case 200:
          final data = response.data;
          if (data == null) {
            throw const GitCodeApiException('接口返回为空，请稍后重试');
          }
          return GitCodeUser.fromJson(data);
        case 401:
          throw const GitCodeApiException('未授权，请检查 access token 或权限');
        case 404:
          if (allowSearchFallback &&
              personalToken != null &&
              personalToken.isNotEmpty) {
            // 优先通过搜索接口尝试“昵称 -> 登录名”的映射，提升用户输入容错度。
            final fallbackLogin = await _searchLoginByKeyword(
              trimmed,
              personalToken: personalToken,
            );
            if (fallbackLogin != null && fallbackLogin != trimmed) {
              // 一旦拿到真实 login，关闭二次 fallback，以免造成递归。
              return fetchUser(
                fallbackLogin,
                personalToken: personalToken,
                allowSearchFallback: false,
              );
            }
          }
          throw GitCodeApiException(
            '未找到用户 $trimmed，若输入的是展示昵称，请改用登录名或携带 token 搜索。',
          );
        default:
          throw GitCodeApiException(
            '查询失败 (HTTP $statusCode)，请稍后重试',
          );
      }
    } on DioException catch (error) {
      if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('请求超时，请检查网络后重试');
      }
      if (error.type == DioExceptionType.badResponse) {
        // 服务器已响应，但状态码 >= 400，由于 validateStatus 只放行 < 500，此处意味着 4xx。
        throw GitCodeApiException(
          '请求异常：HTTP ${error.response?.statusCode ?? '-'}',
        );
      }
      // 其它类型（取消、未知等）直接透传人类可读信息。
      throw GitCodeApiException(error.message ?? '未知网络错误');
    }
  }

  /// 构造通用请求头，必要时附加 Bearer token。
  Map<String, String> _buildHeaders(String? personalToken) {
    return {
      if (personalToken != null && personalToken.isNotEmpty)
        'Authorization': 'Bearer $personalToken',
    };
  }

  /// 当用户输入昵称时，通过搜索接口尝试获取真实 login。
  Future<String?> _searchLoginByKeyword(
    String keyword, {
    required String personalToken,
  }) async {
    try {
      final response = await _dio.get<List<dynamic>>(
        '/search/users',
        queryParameters: {
          'q': keyword,
          'per_page': 1,
          'access_token': personalToken,
        },
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      if ((response.statusCode ?? 0) == 200) {
        final list = response.data;
        if (list == null || list.isEmpty) {
          return null;
        }
        final first = list.first;
        if (first is Map<String, dynamic>) {
          // 只取第一个候选项的 login，命中率通常最高。
          return first['login'] as String?;
        }
      } else if ((response.statusCode ?? 0) == 401) {
        // Token 不合法时立即抛出，提示用户调整配置。
        throw const GitCodeApiException('搜索需要有效的 access token');
      }
    } on DioException {
      return null;
    }
    return null;
  }

  /// 调用 `/search/users`，返回符合关键字的用户简要信息。
  Future<List<GitCodeSearchUser>> searchUsers({
    required String keyword,
    required String personalToken,
    int perPage = 10,
    int page = 1,
  }) async {
    // 用户搜索同样需要去除前后空格，避免无效查询。
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    final response = await _dio.get<List<dynamic>>(
      '/search/users',
      queryParameters: <String, dynamic>{
        'q': trimmed,
        'access_token': personalToken,
        // clamp 可以阻止业务层传入不合法的分页参数，避免后端报错。
        'per_page': perPage.clamp(1, 50),
        'page': page.clamp(1, 100),
      },
      options: Options(
        headers: _buildHeaders(personalToken),
        responseType: ResponseType.json,
        validateStatus: (status) => status != null && status < 500,
      ),
    );

    final statusCode = response.statusCode ?? 0;
    if (statusCode == 401) {
      throw const GitCodeApiException('Token 无效或权限不足，无法搜索用户');
    }
    if (statusCode != 200 || response.data == null) {
      throw GitCodeApiException('搜索用户失败 (HTTP $statusCode)');
    }

    return response.data!
        .whereType<Map<String, dynamic>>()
        .map(GitCodeSearchUser.fromJson)
        .toList();
  }

  /// 调用 `/search/repositories`，支持语言、排序等可选参数。
  Future<List<GitCodeRepository>> searchRepositories({
    required String keyword,
    required String personalToken,
    String? language,
    String? sort,
    String? order,
    int perPage = 10,
    int page = 1,
  }) async {
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    final queryParameters = <String, dynamic>{
      'q': trimmed,
      'access_token': personalToken,
      'per_page': perPage.clamp(1, 50),
      'page': page.clamp(1, 100),
      // 以下三个参数均为可选过滤项，后端若为空会忽略。
      if (language != null && language.isNotEmpty) 'language': language,
      if (sort != null && sort.isNotEmpty) 'sort': sort,
      if (order != null && order.isNotEmpty) 'order': order,
    };

    final response = await _dio.get<List<dynamic>>(
      '/search/repositories',
      queryParameters: queryParameters,
      options: Options(
        headers: _buildHeaders(personalToken),
        responseType: ResponseType.json,
        validateStatus: (status) => status != null && status < 500,
      ),
    );

    final statusCode = response.statusCode ?? 0;
    if (statusCode == 401) {
      throw const GitCodeApiException('Token 无效或权限不足，无法搜索仓库');
    }
    if (statusCode != 200 || response.data == null) {
      throw GitCodeApiException('搜索仓库失败 (HTTP $statusCode)');
    }

    return response.data!
        .whereType<Map<String, dynamic>>()
        .map(GitCodeRepository.fromJson)
        .toList();
  }

  /// 通过 owner 和 repo 名称获取仓库详情，可选地携带 token 进行授权访问。
  /// 支持通过项目 ID 或 owner/repo 路径两种方式查询。
  /// 如果 /repos/ 路径返回 400 或 404，会自动尝试 /projects/ 路径。
  Future<GitCodeRepositoryDetail> fetchRepository(
    String owner,
    String repo, {
    String? personalToken,
    int? projectId,
  }) async {
    final trimmedOwner = owner.trim();
    final trimmedRepo = repo.trim();
    if (trimmedOwner.isEmpty || trimmedRepo.isEmpty) {
      throw const GitCodeApiException('仓库所有者或名称不能为空');
    }

    // 优先使用项目 ID（如果提供）
    if (projectId != null && projectId > 0) {
      try {
        return await _fetchRepositoryByPath('/projects/$projectId', personalToken, trimmedOwner, trimmedRepo);
      } on GitCodeApiException catch (e) {
        // 如果是权限问题，直接抛出错误
        if (e.message.contains('未授权') || e.message.contains('Token')) {
          rethrow;
        }
        // 如果是 404 或其他错误，继续尝试路径方式（可能 projectId 不正确）
        // 继续执行下面的路径查询逻辑
      }
    }

    // 先尝试 /repos/ 路径
    final encodedOwner = Uri.encodeComponent(trimmedOwner);
    final encodedRepo = Uri.encodeComponent(trimmedRepo);
    final reposPath = '/repos/$encodedOwner/$encodedRepo';
    
    try {
      return await _fetchRepositoryByPath(reposPath, personalToken, trimmedOwner, trimmedRepo);
    } on GitCodeApiException catch (e) {
      // 如果 /repos/ 路径返回 400 或 404，尝试使用 /projects/ 路径（GitLab/GitCode 风格）
      if (e.message.contains('HTTP 400') || 
          e.message.contains('请求参数错误') ||
          e.message.contains('HTTP 404') ||
          e.message.contains('未找到仓库')) {
        // 尝试使用 projects 路径，格式为 /projects/{owner}%2F{repo}
        final projectPath = '/projects/${Uri.encodeComponent('$trimmedOwner/$trimmedRepo')}';
        try {
          return await _fetchRepositoryByPath(projectPath, personalToken, trimmedOwner, trimmedRepo);
        } catch (fallbackError) {
          // 如果 projects 路径也失败，抛出更详细的错误信息
          if (fallbackError is GitCodeApiException) {
            throw GitCodeApiException(
              '尝试了多种方式均失败：\n'
              '1. /repos/$trimmedOwner/$trimmedRepo: ${e.message}\n'
              '2. /projects/$trimmedOwner/$trimmedRepo: ${fallbackError.message}',
            );
          }
          rethrow;
        }
      }
      rethrow;
    }
  }

  /// 内部方法：通过指定路径获取仓库详情
  Future<GitCodeRepositoryDetail> _fetchRepositoryByPath(
    String apiPath,
    String? personalToken,
    String owner,
    String repo,
  ) async {
    try {
      final response = await _dio.get<Map<String, dynamic>>(
        apiPath,
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      switch (statusCode) {
        case 200:
          final data = response.data;
          if (data == null) {
            throw const GitCodeApiException('接口返回为空，请稍后重试');
          }
          return GitCodeRepositoryDetail.fromJson(data);
        case 400:
          // HTTP 400 可能是路径格式问题
          String errorMsg = '请求参数错误 (HTTP 400)';
          final responseData = response.data;
          if (responseData is Map<String, dynamic>) {
            final message = responseData['message'] as String?;
            if (message != null && message.isNotEmpty) {
              errorMsg = '请求失败：$message';
            }
          }
          throw GitCodeApiException(
            '$errorMsg\n仓库路径: $owner/$repo\nAPI 路径: $apiPath',
          );
        case 401:
          throw const GitCodeApiException('未授权，请检查 access token 或权限');
        case 404:
          throw GitCodeApiException(
            '未找到仓库 $owner/$repo',
          );
        default:
          // 尝试从响应中获取详细错误信息
          String errorMsg = '查询失败 (HTTP $statusCode)，请稍后重试';
          final data = response.data;
          if (data is Map<String, dynamic>) {
            final message = data['message'] as String?;
            if (message != null && message.isNotEmpty) {
              errorMsg = '查询失败：$message';
            }
          }
          throw GitCodeApiException(errorMsg);
      }
    } on DioException catch (error) {
      if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('请求超时，请检查网络后重试');
      }
      if (error.type == DioExceptionType.badResponse) {
        final statusCode = error.response?.statusCode ?? 0;
        String errorMsg = '请求异常：HTTP $statusCode';
        
        // 尝试从响应中获取详细错误信息
        try {
          final responseData = error.response?.data;
          if (responseData is Map<String, dynamic>) {
            final message = responseData['message'] as String?;
            if (message != null && message.isNotEmpty) {
              errorMsg = '请求失败：$message';
            }
          } else if (responseData is String && responseData.isNotEmpty) {
            errorMsg = '请求失败：$responseData';
          }
        } catch (_) {
          // 忽略解析错误
        }
        
        throw GitCodeApiException(errorMsg);
      }
      throw GitCodeApiException(error.message ?? '未知网络错误');
    }
  }
}

class GitCodeUser {
  GitCodeUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.bio,
    this.htmlUrl,
    this.publicRepos,
    this.followers,
    this.following,
    this.createdAt,
  });

  factory GitCodeUser.fromJson(Map<String, dynamic> json) {
    return GitCodeUser(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      bio: json['bio'] as String?,
      htmlUrl: json['html_url'] as String?,
      publicRepos: _safeInt(json['public_repos']),
      followers: _safeInt(json['followers']),
      following: _safeInt(json['following']),
      createdAt: json['created_at'] as String?,
    );
  }

  /// GitCode 唯一登录名，可用于后续接口请求。
  final String login;

  /// 头像的完整 URL，用于展示用户头像。
  final String avatarUrl;

  /// 用户在个人资料中填写的展示昵称，可能为空。
  final String? name;

  /// 个人简介或签名信息。
  final String? bio;

  /// 用户主页链接，通常指向 GitCode Web。
  final String? htmlUrl;

  /// 用户公开仓库的数量。
  final int? publicRepos;

  /// 粉丝（followers）数量。
  final int? followers;

  /// 关注（following）数量。
  final int? following;

  /// 账号创建时间，ISO 时间字符串。
  final String? createdAt;
}

/// 将接口返回的动态数值转换为 int，兼容字符串与数字。
int? _safeInt(dynamic value) {
  if (value == null) {
    return null;
  }
  if (value is int) {
    return value;
  }
  if (value is String) {
    // 有些字段可能以字符串形式返回整型，此处尝试解析。
    return int.tryParse(value);
  }
  return null;
}

class GitCodeApiException implements Exception {
  const GitCodeApiException(this.message);

  final String message;

  @override
  String toString() => 'GitCodeApiException: $message';
}

class GitCodeSearchUser {
  GitCodeSearchUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.htmlUrl,
    this.createdAt,
  });

  factory GitCodeSearchUser.fromJson(Map<String, dynamic> json) {
    return GitCodeSearchUser(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      htmlUrl: json['html_url'] as String?,
      createdAt: json['created_at'] as String?,
    );
  }

  /// 搜索结果中的登录名。
  final String login;

  /// 搜索结果中的头像地址。
  final String avatarUrl;

  /// 搜索结果中的展示名称。
  final String? name;

  /// 用户主页链接。
  final String? htmlUrl;

  /// 用户创建时间。
  final String? createdAt;
}

class GitCodeRepository {
  GitCodeRepository({
    required this.fullName,
    required this.webUrl,
    this.description,
    this.language,
    this.updatedAt,
    this.stars,
    this.forks,
    this.watchers,
    this.ownerLogin,
    this.isPrivate,
    this.id,
    this.projectId,
  });

  factory GitCodeRepository.fromJson(Map<String, dynamic> json) {
    final owner = json['owner'];
    return GitCodeRepository(
      fullName: json['full_name'] as String? ?? '',
      webUrl: json['web_url'] as String? ?? '',
      description: json['description'] as String?,
      language: json['language'] as String?,
      updatedAt: json['updated_at'] as String?,
      stars: _safeInt(json['stargazers_count']) ?? _safeInt(json['stars_count']),
      forks: _safeInt(json['forks_count']),
      watchers: _safeInt(json['watchers_count']),
      ownerLogin: owner is Map<String, dynamic>
          // 兼容不同字段命名（name/path），以防接口字段发生差异。
          ? owner['name'] as String? ?? owner['path'] as String?
          : null,
      isPrivate: _safeBool(json['private']),
      // 尝试获取项目 ID，可能在不同字段中
      id: _safeInt(json['id']),
      projectId: _safeInt(json['project_id']) ?? _safeInt(json['id']),
    );
  }

  /// 仓库的全名，形如 "owner/repo"。
  final String fullName;

  /// 仓库在 GitCode 上的访问 URL。
  final String webUrl;

  /// 仓库描述，可能为空。
  final String? description;

  /// 主要编程语言。
  final String? language;

  /// 最近一次更新时间。
  final String? updatedAt;

  /// Stargazers 数量，优先读取 `stargazers_count`。
  final int? stars;

  /// Fork 数量。
  final int? forks;

  /// Watchers 数量。
  final int? watchers;

  /// 仓库所有者的登录名或路径。
  final String? ownerLogin;

  /// 是否私有仓库。
  final bool? isPrivate;

  /// 仓库 ID（如果 API 返回）
  final int? id;

  /// 项目 ID（用于查询详情，可能在不同字段中）
  final int? projectId;
}

/// GitCode 接口可能返回 0/1、'true'/'false'，统一转成 bool。
bool? _safeBool(dynamic value) {
  if (value == null) {
    return null;
  }
  if (value is bool) {
    return value;
  }
  if (value is int) {
    // GitCode 某些布尔字段会用 0/1 表示。
    return value != 0;
  }
  if (value is String) {
    if (value == '1' || value.toLowerCase() == 'true') {
      return true;
    }
    if (value == '0' || value.toLowerCase() == 'false') {
      return false;
    }
  }
  return null;
}

/// 仓库详情模型，包含更完整的仓库信息
class GitCodeRepositoryDetail {
  GitCodeRepositoryDetail({
    required this.id,
    required this.name,
    required this.fullName,
    required this.htmlUrl,
    this.description,
    this.language,
    this.stars,
    this.forks,
    this.watchers,
    this.openIssues,
    this.defaultBranch,
    this.createdAt,
    this.updatedAt,
    this.pushedAt,
    this.size,
    this.isPrivate,
    this.isArchived,
    this.isFork,
    this.owner,
    this.license,
  });

  factory GitCodeRepositoryDetail.fromJson(Map<String, dynamic> json) {
    final ownerData = json['owner'];
    final licenseData = json['license'];
    return GitCodeRepositoryDetail(
      id: _safeInt(json['id']) ?? 0,
      name: json['name'] as String? ?? '',
      fullName: json['full_name'] as String? ?? '',
      htmlUrl: json['html_url'] as String? ?? json['web_url'] as String? ?? '',
      description: json['description'] as String?,
      language: json['language'] as String?,
      stars: _safeInt(json['stargazers_count']) ?? _safeInt(json['stars_count']),
      forks: _safeInt(json['forks_count']),
      watchers: _safeInt(json['watchers_count']),
      openIssues: _safeInt(json['open_issues_count']),
      defaultBranch: json['default_branch'] as String?,
      createdAt: json['created_at'] as String?,
      updatedAt: json['updated_at'] as String?,
      pushedAt: json['pushed_at'] as String?,
      size: _safeInt(json['size']),
      isPrivate: _safeBool(json['private']),
      isArchived: _safeBool(json['archived']),
      isFork: _safeBool(json['fork']),
      owner: ownerData is Map<String, dynamic>
          ? RepositoryOwner.fromJson(ownerData)
          : null,
      license: licenseData is Map<String, dynamic>
          ? RepositoryLicense.fromJson(licenseData)
          : null,
    );
  }

  /// 仓库 ID
  final int id;

  /// 仓库名称
  final String name;

  /// 仓库全名，形如 "owner/repo"
  final String fullName;

  /// 仓库在 GitCode 上的访问 URL
  final String htmlUrl;

  /// 仓库描述
  final String? description;

  /// 主要编程语言
  final String? language;

  /// Star 数量
  final int? stars;

  /// Fork 数量
  final int? forks;

  /// Watchers 数量
  final int? watchers;

  /// 打开的 Issues 数量
  final int? openIssues;

  /// 默认分支
  final String? defaultBranch;

  /// 创建时间
  final String? createdAt;

  /// 更新时间
  final String? updatedAt;

  /// 最后推送时间
  final String? pushedAt;

  /// 仓库大小（KB）
  final int? size;

  /// 是否私有仓库
  final bool? isPrivate;

  /// 是否已归档
  final bool? isArchived;

  /// 是否为 Fork
  final bool? isFork;

  /// 仓库所有者
  final RepositoryOwner? owner;

  /// 许可证信息
  final RepositoryLicense? license;
}

/// 仓库所有者信息
class RepositoryOwner {
  RepositoryOwner({
    required this.login,
    this.avatarUrl,
    this.htmlUrl,
    this.type,
  });

  factory RepositoryOwner.fromJson(Map<String, dynamic> json) {
    return RepositoryOwner(
      login: json['login'] as String? ?? json['name'] as String? ?? json['path'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String?,
      htmlUrl: json['html_url'] as String?,
      type: json['type'] as String?,
    );
  }

  final String login;
  final String? avatarUrl;
  final String? htmlUrl;
  final String? type;
}

/// 仓库许可证信息
class RepositoryLicense {
  RepositoryLicense({
    this.key,
    this.name,
    this.spdxId,
    this.url,
  });

  factory RepositoryLicense.fromJson(Map<String, dynamic> json) {
    return RepositoryLicense(
      key: json['key'] as String?,
      name: json['name'] as String?,
      spdxId: json['spdx_id'] as String?,
      url: json['url'] as String?,
    );
  }

  final String? key;
  final String? name;
  final String? spdxId;
  final String? url;
}

